<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片逐步碎片化</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f4f4f9;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
        }

        .input-section {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;
            display: flex;
            flex-direction: column;
        }

        .canvas-section {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
        }

        .canvas-container {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        canvas {
            border: 1px solid #ccc;
            max-width: 100%;
            height: auto;
        }

        /* 为按钮添加一些外边距 */
        .input-section button {
            margin-top: 10px;
        }

        /* 滑动开关样式 */
        .switch {
            position: relative;
            display: inline-block;
            width: 60px;
            height: 34px;
        }

        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            -webkit-transition: .4s;
            transition: .4s;
            border-radius: 34px;
        }

        .slider:before {
            position: absolute;
            content: "";
            height: 26px;
            width: 26px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            -webkit-transition: .4s;
            transition: .4s;
            border-radius: 50%;
        }

        input:checked + .slider {
            background-color: #2196F3;
        }

        input:focus + .slider {
            box-shadow: 0 0 1px #2196F3;
        }

        input:checked + .slider:before {
            -webkit-transform: translateX(26px);
            -ms-transform: translateX(26px);
            transform: translateX(26px);
        }
    </style>
</head>

<body class="flex flex-col items-center min-h-screen">
    <div class="container">
        <div class="input-section">
            <h1 class="text-3xl font-bold text-center text-gray-800 mb-6">图片逐步碎片化</h1>
            <div class="text-center mb-6">
                <p class="text-gray-600">图片体积过大可能会导致浏览器卡顿（耐心等待即可），具体和设备配置有关。</p>
                <p class="text-gray-600">本程序属于纯前端技术实现，服务器不会保存任何数据。</p>
            </div>
            <label for="imageInput" class="block text-gray-700 text-sm font-bold mb-2">选择图片:</label>
            <input type="file" id="imageInput" class="border border-gray-300 p-2 rounded-md">
            <label for="invertColors" class="block text-gray-700 text-sm font-bold mb-2 mt-4">是否反色:</label>
            <label class="switch">
                <input type="checkbox" id="invertColors">
                <span class="slider"></span>
            </label>
            <label for="shapeSize" class="block text-gray-700 text-sm font-bold mb-2 mt-4">形状大小 (px):</label>
            <input type="number" id="shapeSize" value="50" class="border border-gray-300 p-2 rounded-md">
            <label for="shapesPerStep" class="block text-gray-700 text-sm font-bold mb-2 mt-4">每次抠图形状数量:</label>
            <input type="number" id="shapesPerStep" value="50" class="border border-gray-300 p-2 rounded-md">
            <label for="totalSteps" class="block text-gray-700 text-sm font-bold mb-2 mt-4">抠图次数:</label>
            <input type="number" id="totalSteps" value="10" class="border border-gray-300 p-2 rounded-md">
            <button id="processButton"
                class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md mt-4">开始处理</button>
            <button id="downloadButton"
                class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-md mt-4" disabled>一键下载</button>
        </div>
        <div class="canvas-section" id="canvasSection"></div>
    </div>

    <script>
        // 获取DOM元素
        const imageInput = document.getElementById('imageInput');
        const processButton = document.getElementById('processButton');
        const shapeSizeInput = document.getElementById('shapeSize');
        const shapesPerStepInput = document.getElementById('shapesPerStep');
        const totalStepsInput = document.getElementById('totalSteps');
        const canvasSection = document.getElementById('canvasSection');
        const downloadButton = document.getElementById('downloadButton');
        const invertColorsCheckbox = document.getElementById('invertColors');

        // 全局变量
        let image; // 存储上传的图片
        let generatedCanvases = []; // 存储生成的画布集合

        /**
         * 监听图片上传事件
         * 读取用户选择的图片并转换为Image对象
         */
        imageInput.addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = (event) => {
                    image = new Image();
                    image.src = event.target.result;
                };
                reader.onerror = () => {
                    alert('文件读取失败，请重试！');
                };
                reader.readAsDataURL(file);
            }
        });

        /**
         * 生成随机圆形区域
         * @param {HTMLCanvasElement} baseCanvas - 基础画布
         * @param {number} shapeSize - 圆形大小
         * @param {number} shapesPerStep - 每步生成的圆形数量
         * @returns {Array} - 返回圆形数组，每个圆包含x,y坐标和半径
         */
        function generateCircles(baseCanvas, shapeSize, shapesPerStep) {
            const circles = [];
            for (let i = 0; i < shapesPerStep; i++) {
                const x = Math.random() * baseCanvas.width;
                const y = Math.random() * baseCanvas.height;
                const circle = { x, y, radius: shapeSize / 2 };
                circles.push(circle);
            }
            return circles;
        }

        /**
         * 从图像中抠出圆形区域
         * @param {HTMLCanvasElement} baseCanvas - 基础画布
         * @param {Array} circles - 圆形区域数组
         * @param {Object} usedPixels - 已使用像素的记录对象
         * @returns {HTMLCanvasElement} - 返回处理后的画布
         */
        function cutoutImage(baseCanvas, circles, usedPixels) {
            const cutoutCanvas = document.createElement('canvas');
            const cutoutCtx = cutoutCanvas.getContext('2d');
            cutoutCanvas.width = baseCanvas.width;
            cutoutCanvas.height = baseCanvas.height;
            cutoutCtx.drawImage(baseCanvas, 0, 0);

            const imageData = cutoutCtx.getImageData(0, 0, cutoutCanvas.width, cutoutCanvas.height);
            const data = imageData.data;
            for (let j = 0; j < data.length; j += 4) {
                const px = (j / 4) % cutoutCanvas.width;
                const py = Math.floor((j / 4) / cutoutCanvas.width);
                let isInsideCurrentCircles = false;
                for (const circle of circles) {
                    const distanceSquared = (px - circle.x) * (px - circle.x) + (py - circle.y) * (py - circle.y);
                    if (distanceSquared <= circle.radius * circle.radius) {
                        isInsideCurrentCircles = true;
                        usedPixels[px + py * cutoutCanvas.width] = true;
                        break;
                    }
                }
                if (!isInsideCurrentCircles) {
                    data[j] = 255;
                    data[j + 1] = 255;
                    data[j + 2] = 255;
                }
            }
            cutoutCtx.putImageData(imageData, 0, 0);
            return cutoutCanvas;
        }

        function extractRemainingArea(baseCanvas, usedPixels) {
            const remainingCanvas = document.createElement('canvas');
            const remainingCtx = remainingCanvas.getContext('2d');
            remainingCanvas.width = baseCanvas.width;
            remainingCanvas.height = baseCanvas.height;
            remainingCtx.drawImage(baseCanvas, 0, 0);

            const imageData = remainingCtx.getImageData(0, 0, remainingCanvas.width, remainingCanvas.height);
            const data = imageData.data;
            for (let j = 0; j < data.length; j += 4) {
                const px = (j / 4) % remainingCanvas.width;
                const py = Math.floor((j / 4) / remainingCanvas.width);
                if (usedPixels[px + py * remainingCanvas.width]) {
                    data[j] = 255;
                    data[j + 1] = 255;
                    data[j + 2] = 255;
                }
            }
            remainingCtx.putImageData(imageData, 0, 0);
            return remainingCanvas;
        }

        function createCanvasContainer(canvas, title) {
            const container = document.createElement('div');
            container.classList.add('canvas-container');
            container.innerHTML = `<h2 class="text-xl font-bold text-gray-800 mb-2">${title}</h2>`;
            container.appendChild(canvas);
            return container;
        }

        function invertColors(canvas) {
            const ctx = canvas.getContext('2d');
            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            const data = imageData.data;
            for (let i = 0; i < data.length; i += 4) {
                data[i] = 255 - data[i];
                data[i + 1] = 255 - data[i + 1];
                data[i + 2] = 255 - data[i + 2];
            }
            ctx.putImageData(imageData, 0, 0);
            return canvas;
        }

        /**
         * 处理按钮点击事件
         * 根据用户设置的参数生成逐步碎片化的图像
         */
        processButton.addEventListener('click', async () => {
            // 清空
            canvasSection.innerHTML = '';
            generatedCanvases = [];
            downloadButton.disabled = true;

            if (!image) {
                alert('请先选择图片！');
                return;
            }

            const shapeSize = parseInt(shapeSizeInput.value);
            const shapesPerStep = parseInt(shapesPerStepInput.value);
            const totalSteps = parseInt(totalStepsInput.value);
            const shouldInvertColors = invertColorsCheckbox.checked;

            if (isNaN(shapeSize) || shapeSize <= 0 || isNaN(shapesPerStep) || shapesPerStep <= 0 || isNaN(totalSteps) || totalSteps <= 0) {
                alert('请输入有效的正整数！');
                return;
            }

            // 添加加载提示
            const loadingDiv = document.createElement('div');
            loadingDiv.textContent = '正在处理，请稍候...';
            canvasSection.appendChild(loadingDiv);

            const baseCanvas = document.createElement('canvas');
            const baseCtx = baseCanvas.getContext('2d');
            baseCanvas.width = image.width;
            baseCanvas.height = image.height;
            baseCtx.drawImage(image, 0, 0);

            if (shouldInvertColors) {
                invertColors(baseCanvas);
            }

            const usedPixels = new Array(baseCanvas.width * baseCanvas.height).fill(false);

            try {
                for (let step = 0; step < totalSteps - 1; step++) {
                    const currentCircles = generateCircles(baseCanvas, shapeSize, shapesPerStep);
                    const cutoutCanvas = cutoutImage(baseCanvas, currentCircles, usedPixels);
                    generatedCanvases.push(cutoutCanvas);

                    const cutoutContainer = createCanvasContainer(cutoutCanvas, `次数 ${step + 1} 抠图结果`);
                    canvasSection.appendChild(cutoutContainer);
                }

                // 提取剩余区域
                const remainingCanvas = extractRemainingArea(baseCanvas, usedPixels);
                generatedCanvases.push(remainingCanvas);

                const remainingContainer = createCanvasContainer(remainingCanvas, '剩余区域抠图结果');
                canvasSection.appendChild(remainingContainer);

                // 移除加载提示
                canvasSection.removeChild(loadingDiv);

                downloadButton.disabled = false;
            } catch (error) {
                canvasSection.removeChild(loadingDiv);
                alert(`处理失败：${error.message}`);
            }
        });

        /**
         * 下载按钮点击事件
         * 将所有生成的图片打包成zip文件供用户下载
         */
        downloadButton.addEventListener('click', async () => {
            if (generatedCanvases.length === 0) {
                alert('请先生成图片！');
                return;
            }

            const zip = new JSZip();
            const imgFolder = zip.folder("碎片化图片");

            // 添加每个画布到zip
            for (let i = 0; i < generatedCanvases.length; i++) {
                const canvas = generatedCanvases[i];
                const dataURL = canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, "");
                imgFolder.file(`步骤${i + 1}.png`, dataURL, { base64: true });
            }

            // 生成并下载zip
            zip.generateAsync({ type: "blob" }).then(function (content) {
                saveAs(content, "碎片化图片.zip");
            });
        });
    </script>
</body>

</html>